﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Logging;

using Microsoft.Cci;

using PartialExpressionCompleter.Query;
using PartialExpressionCompleter.Util;

namespace PartialExpressionCompleter.Experiment
{
    class ArgumentQueryExperiment : MethodExperiment
    {
        public ArgumentQueryExperiment(IAssembly unit)
            : base(unit)
        { }
        public override string FileExtension
        {
            get { return "argquery"; }
        }

        protected override void DoMethodCallAction(QueryContext<IMethodCall> context)
        {
            var calle = context.OriginalExpressionAsQuery as MethodCall;

            var simpleExps = context.GetAllSimpleExpressions()
                .GroupBy(e => e.Type).ToList();

            Logger.PrintLine("Enclosing type: " + context.ContainingType);
            Logger.PrintLine("Enclosing method: " + context.ContainingMethod);
            Logger.PrintLine("Original call: " + calle);
            Logger.PrintLine();
            int i = -1;
            foreach (var tup in calle.Method.ResolvedMethod.GetParameterTypes()
                                    .Zip(calle.Arguments, (p, a) => p.PairWith(a)))
            {
                i++;
                var argStart = DateTime.Now;

                var param = tup.Item1;
                var arg = tup.Item2;

                List<Expression> possibleExps;
                IEnumerable<IGrouping<ITypeDefinition, Expression>> possibleExpsQuery;
                if (param is IManagedPointerTypeReference)
                {
                    possibleExps = context.GetAllSimpleExpressions()
                        .Where(e => e is FieldLookup || e is StaticFieldLookup
                                 || e is LocalVariable || e is ParamVariable)
                        .Select(e => e.Type is IManagedPointerTypeReference
                            ? e
                            : (Expression)new AddressOf(
                                e.Type.CreateManagedPointerType().ResolvedType, e))
                        .Where(e => param.IsAssignableFrom(e.Type, false))
                        .ToList();
                }
                else
                {
                    possibleExpsQuery = simpleExps;
                    possibleExps = possibleExpsQuery
                        .Where(g => param.IsAssignableFrom(g.Key, false))
                        .SelectMany(g => g)
                        .ToList();

                    /*var nullExp = new CompileTimeConstant(arg.Type, null);
                    nullExp.BaseCost = Expression.CONSTANT_COST;
                    possibleExps.Add(nullExp);*/
                }
                
                var possibleExpsCount = possibleExps.Count;

                Logger.PrintLine("Arg: " + arg);
                Logger.PrintLine("ArgExpType: " + arg.GetType());
                Logger.PrintLine("Param type: " + param);
                Logger.PrintLine("Possible total zero-arg: "
                    + possibleExpsCount);

                string guessable = "not_guessable";
                string score = "";
                if (arg is BinaryOperation
                    || arg is UnaryOperation
                    || arg is Cast
                    || arg is CreateArray
                    || arg is CreateDelegate
                    || arg is TypeOf
                    || arg is UnsupportedCompleteExpression
                    || arg is ArrayLookup
                    || (arg is MethodCall && ((arg as MethodCall).Method.ParameterCount > 0
                        || (arg as MethodCall).Method.ResolvedMethod.IsConstructor)))
                {
                    Logger.PrintLine("Not guessable out of " + possibleExps.Count);
                }
                else if (possibleExps.Contains(arg))
                {
                    guessable = "guessed";
                    score = ComputeScore(calle, possibleExps, i, context, arg).ToString();
                    Logger.PrintLine("Possible zero-args contains arg!");
                    Logger.PrintLine("Rank: "
                        + score
                        + " / " + possibleExps.Count);
                }
                else
                {
                    if (arg is CompileTimeConstant)
                    {
                        Logger.PrintLine("Not guessable out of " + possibleExps.Count);
                    }
                    else
                    {
                        guessable = "not_guessed";
                        Logger.PrintLine("Not guessed out of " + possibleExps.Count);
                    }
                }
                var time = DateTime.Now - argStart;

                Logger.PrintLine("Time to compute for entire arg: "
                    + time.TotalSeconds + " seconds");

                Logger.PrintLine("ArgInfoTuple: " + arg.GetType() + "," + guessable
                    + "," + possibleExps.Count + "," + score + "," + time.TotalSeconds);


                Logger.PrintLine();
            }
            Logger.PrintLine();
        }

        protected int ComputeScore(MethodCall calle, List<Expression> possibleExps,
                int index, QueryContext<IMethodCall> context, Expression correctAnswer)
        {
            var args = new List<Expression>(
                Enumerable.Repeat(UnknownExpression.Instance,
                                  calle.Arguments.Count));
            var correctCost = calle
                .ComputeScore(context, args.WithValueSet(
                    possibleExps.First(e => e.Equals(correctAnswer)), index));
            var scoredExps = possibleExps
                //.Where(e => e.BaseCost <= correctCost)
                .Select(e => e.PairWith(
                    calle.ComputeScore(context, args.WithValueSet(e, index))))
                .OrderBy(t => t.Item2)
                .ToList();
            /*
            foreach (var guess in scoredExps.Take(10))
            {
                Logger.PrintLine("Guess: " + guess);
            }*/
            return (scoredExps.TakeWhile(t => !t.Item1.Equals(correctAnswer)).Count() + 1);
        }
    }
}
